#ifndef __HardwareBufferManager__
#define __HardwareBufferManager__

#include "Singleton.h"
#include "HardwareVertexBuffer.h"
#include "HardwareIndexBuffer.h"
#include "RenderToVertexBuffer.h"

namespace PVM
{
	class HardwareBufferLicensee
	{
	public:
		virtual ~HardwareBufferLicensee() {}
		virtual void licenseExpired(HardwareBuffer* buffer) = 0;
	};
	
	class TempBlendedBufferInfo : public HardwareBufferLicensee, public BufferAlloc
	{
	public:
		HardwareVertexBufferSharedPtr srcPositionBuffer;
		HardwareVertexBufferSharedPtr srcNormalBuffer;
		
		HardwareVertexBufferSharedPtr destPositionBuffer;
		HardwareVertexBufferSharedPtr destNormalBuffer;
		
		bool posNormalShareBuffer;
		unsigned short posBindIndex;
		unsigned short normBindIndex;
		bool bindPositions;
		bool bindNormals;
		
	public:
		~TempBlendedBufferInfo(void);
		void extractFrom(const VertexData* sourceData);
		void checkoutTempCopies(bool positions = true, bool normals = true);
		void bindTempCopies(VertexData* targetData, bool suppressHardwareUpload);
		void licenseExpired(HardwareBuffer* buffer);
		bool buffersCheckedOut(bool positions = true, bool normals = true) const;
	};
	
	class HardwareBufferManagerBase : public BufferAlloc
	{
		friend class HardwareVertexBufferSharedPtr;
		friend class HardwareIndexBufferSharedPtr;
		
	protected:
		typedef set<HardwareVertexBuffer*>::type VertexBufferList;
		typedef set<HardwareIndexBuffer*>::type IndexBufferList;
		VertexBufferList mVertexBuffers;
		IndexBufferList; mIndexBuffers;
		
		typedef set<VertexDeclaration*>::type VertexDeclarationList;
		typedef set<VertexBufferBinding*>::type VertexBufferBindingList;
		VertexDeclarationList mVertexDeclarations;
		VertexBufferBindingList mVertexBufferBindings;
		
		PVM_MUTEX(mVertexBuffersMutex)
		PVM_MUTEX(mIndexBuffersMutex)
		PVM_MUTEX(mVertexDeclarationsMutex)
		PVM_MUTEX(mVertexBufferBindingsMutex)
		
		virtual void destroyAllDeclarations(void);
		virtual void destroyAllBindings(void);
		virtual VertexDeclaration* createVertexDeclarationImpl(void);
		virtual void destroyVertexDeclarationImpl(VertexDeclaration* decl);
		virtual VertexBufferBinding* createVertexBufferBindingImpl(void);
		virtual void destroyVertexBufferBindingImpl(VertexBufferBinding* binding);
		
	public:
		enum BufferLicenseType
		{
			BLT_MANUAL_RELEASE,
			BLT_AUTOMATIC_RELEASE
		};
		
	protected:
		class VertexBufferLicense
		{
		public:
			HardwareVertexBuffer* originalBufferPtr;
			BufferLicenseType licenseType;
			size_t expiredDelay;
			HardwareVertexBufferSharedPtr buffer;
			HardwareBufferLicensee* licensee;
			VertexBufferLicense(
				HardwareVertexBuffer* orig,
				BufferLicenseType ltype,
				size_t delay,
				HardwareVertexBufferSharedPtr buf,
				HardwareBufferLicensee* lic)
			: originalBufferPtr(orig)
			, licenseType(ltype)
			, expiredDelay(delay)
			, buffer(buf)
			, licensee(lic)
			{}
		};
		
		typedef multimap<HardwareVertexBuffer*, HardwareBufferSharedPtr>::type FreeTemporaryVertexBufferMap;
		FreeTemporaryVertexBufferMap mFreeTempVertexBufferMap;
		typedef map<HardwareVertexBuffer*, VertexBufferLicense>::type TemporaryVertexBufferLicenseMap;
		TemporaryVertexBufferLicenseMap mTempVertexBufferLicenses;
		
		size_t mUnderUsedFrameCount;
		static const size_t UNDER_USED_FRAME_THRESHOLD;
		static const size_t EXPIRED_DELAY_FRAME_THRESHOLD;
		
		PVM_MUTEX(mTempBuffersMutex)
		
		virtual HardwareVertexBufferSharedPtr makeBufferCopy(
			const HardwareVertexBufferSharedPtr& source,
				HardwareBuffer::Usage usage, bool useShadowBuffer);
		
	public:
		HardwareBufferManagerBase();
		virtual ~HardwareBufferManagerBase();
		
		virtual HardwareVertexBufferSharedPtr
		createVertexBuffer(size_t vertexSize, size_t numVerts, HardwareBuffer::Usage usage,
						   bool useShadowBuffer = false) = 0;
		virtual HardwareIndexBufferSharedPtr
		createIndexBuffer(HardwareIndexBuffer::IndexType itype, size_t numIndexes,
						  HardwareBuffer::Usage usage, bool useShadowBuffer = false) = 0;
		virtual RenderToVertexBufferSharedPtr createRenderToVertexBuffer() = 0;
		
		virtual VertexDeclaration* createVertexDeclaration(void);
		virtual void destroyVertexDeclaration(VertexDeclaration* decl);
		
		virtual VertexBufferBinding* createVertexBufferBinding(void);
		virtual void destroyVertexBufferBinding(VertexBufferBinding* binding);
		
		virtual void registerVertexBufferSourceAndCopy(
			const HardwareVertexBufferSharedPtr& sourceBuffer,
			const HardwareVertexBufferSharedPtr& copy);
		
		virtual HardwareVertexBufferSharedPtr allocateVertexBufferCopy(
			const HardwareVertexBufferSharedPtr& sourceBuffer,
			BufferLicenseType licenseType,
			HardwareBufferLicensee* licensee,
			bool copyData = false);
		
		virtual void releaseVertexBufferCopy(
			const HardwareVertexBufferSharedPtr& bufferCopy);
		
		virtual void touchVertexBufferCopy(
			const HardwareVertexBufferSharedPtr& bufferCopy);
		
		virtual void _freeUnusedBufferCopies(void);
		
		virtual void _releaseBufferCopies(bool forceFreeUnused = false);
		
		virtual void _forceReleaseBufferCopies(
				const HardwareVertexBufferSharedPtr& sourceBuffer);
		
		virtual void _forceReleaseBufferCopies(HardwareVertexBuffer* sourceBuffer);
		
		void _notifyVertexBufferDestroyed(HardwareVertexBuffer* buf);
		void _notifyIndexBufferDestroyed(HardwareIndexBuffer* buf);
	};
	
	class HardwareBufferManager : public HardwareBufferManagerBase, public Singleton<HardwareBufferManager>
	{
		friend class HardwareVertexBufferSharedPtr;
		friend class HardwareIndexBufferSharedPtr;
	protected:
		HardwareBufferManagerBase* mImpl;
	public:
		HardwareBufferManager(HardwareBufferManagerBase* impl);
		~HardwareBufferManager();
		
		HardwareVertexBufferSharedPtr
		createVertexBuffer(size_t vertexSize, size_t numVerts, HardwareBuffer::Usage usage,
		bool useShadowBuffer = false)
		{
			return mImpl->createVertexBuffer(vertexSize, numVerts, usage, useShadowBuffer);
		}
		
		HardwareIndexBufferSharedPtr
		createIndexBuffer(HardwareIndexBuffer::IndexType itype, size_t numIndexes,
						  HardwareBuffer::Usage usage, bool useShadowBuffer = false)
		{
			return mImpl->createIndexBuffer(itype, numIndexes, usage, useShadowBuffer);
		}
		
		RenderToVertexBufferSharedPtr createRenderToVertexBuffer()
		{
			return mImpl->createRenderToVertexBuffer();
		}
		
		virtual VertexDeclaration* createVertexDeclaration(void)
		{
			return mImpl->createVertexDeclaration();
		}
		
		virtual void destroyVertexDeclaration(VertexDeclaration* decl)
		{
			mImpl->destroyVertexDeclaration(decl);
		}
		
		virtual VertexBufferBinding* createVertexBufferBinding(void)
		{
			return mImpl->createVertexBufferBinding();
		}
		
		virtual void destroyVertexBufferBinding(VertexBufferBinding* binding)
		{
			mImpl->destroyVertexBufferBinding(binding);
		}
		
		virtual void registerVertexBufferSourceAndCopy(
			const HardwareVertexBufferSharedPtr& sourceBuffer,
			const HardwareVertexBufferSharedPtr& copy)
		{
			mImpl->registerVertexBufferSourceAndCopy(sourceBuffer, copy);
		}
		
		virtual HardwareVertexBufferSharedPtr allocateVertexBufferCopy(
		const HardwareVertexBufferSharedPtr& sourceBuffer,
		BufferLicenseType licenseType,
		HardwareBufferLicensee* licensee,
		bool copyData = false)
		{
			return mImpl->allocateVertexBufferCopy(sourceBuffer, licenseType, licensee, copyData);
		}
		
		virtual void releaseVertexBufferCopy(
		const HardwareVertexBufferSharedPtr& bufferCopy)
		{
			mImpl->releaseVertexBufferCopy(bufferCopy);
		}
		
		virtual void touchVertexBufferCopy(
		const HardwareVertexBufferSharedPtr& bufferCopy)
		{
			mImpl->touchVertexBufferCopy(bufferCopy);
		}
		
		virtual void _freeUnusedBufferCopies(void)
		{
			mImpl->_freeUnusedBufferCopies();
		}
		
		virtual void _releaseBufferCopies(bool forceFreeUnused = false)
		{
			mImpl->_releaseBufferCopies(forceFreeUnused);
		}
		
		virtual void _forceReleaseBufferCopies(
		const HardwareVertexBufferSharedPtr& sourceBuffer)
		{
			mImpl->_forceReleaseBufferCopies(sourceBuffer);
		}
		
		virtual void _forceReleaseBufferCopies(HardwareVertexBuffer* sourceBuffer)
		{
			mImpl->_forceReleaseBufferCopies(sourceBuffer);
		}
		
		void _notifyVertexBufferDestroyed(HardwareVertexBuffer* buf)
		{
			mImpl->_notifyVertexBufferDestroyed(buf);
		}
		
		void _notifyIndexBufferDestroyed(HardwareIndexBuffer* buf)
		{
			mImpl->_notifyIndexBufferDestroyed(buf);
		}
		
		static HardwareBufferManager& getSingleton(void);
		
		static HardwareBufferManager* getSingletonPtr(void);
	};
}

#endif
